package com.heima.wemedia.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.apis.article.IArticleClient;
import com.heima.common.dtos.ResponseResult;
import com.heima.common.enums.AppHttpCodeEnum;
import com.heima.common.exception.CustomException;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.bean.ContentBean;
import com.heima.wemedia.bean.ImageCheckResult;
import com.heima.wemedia.bean.TextCheckResult;
import com.heima.wemedia.config.AliYunGreenConfig;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.service.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Transactional
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {

    @Resource
    private WmNewsService wmNewsService;
    @Resource
    private GreenService greenService;
    @Resource
    private WmChannelService wmChannelService;
    @Resource
    private WmUserService wmUserService;
    @Resource
    private IArticleClient articleClient;
    @Resource
    private WmSensitiveService wmSensitiveService;
    @Resource
    private AliYunGreenConfig aliYunGreenConfig;
    @Resource
    private OCRService ocrService;
    @Resource
    private WmSensitiveMapper wmSensitiveMapper;

    private static final int CONTENT_TYPE_TEXT = 1;
    private static final int CONTENT_TYPE_IMAGE = 2;

    @Async
    @Override
    public void autoScanWmNews(Integer id) {
        //1. 根据id查询文章
        WmNews wmNews = this.wmNewsService.getById(id);
        if (ObjectUtil.isEmpty(wmNews)) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        if (ObjectUtil.notEqual(wmNews.getStatus(), WmNews.Status.SUBMIT.getCode())) {
            //非待审核状态，不做处理
            return;
        }

        //2. 审核文章中文本内容
        List<String> textList = this.extractTextOrImages(wmNews, CONTENT_TYPE_TEXT);
        boolean isHandleText = this.handleTextScan(StrUtil.join("", textList), wmNews);
        if (!isHandleText) {
            //存在违规内容
            return;
        }

        //3. 审核文章中的图片内容
        List<String> imageList = this.extractTextOrImages(wmNews, CONTENT_TYPE_IMAGE);
        boolean isHandleImage = this.handleImageScan(imageList, wmNews);
        if (!isHandleImage) {
            return;
        }

        //4. 更新文章状态，审核通过，调用文章微服务发布文章
        ResponseResult<Long> responseResult = this.saveAppArticle(wmNews);
        if (ObjectUtil.notEqual(responseResult.getCode(), 200)) {
            //抛出异常的作用，回滚事务
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR);
        }

        wmNews.setArticleId(responseResult.getData()); //设置文章id
        this.updateWmNews(wmNews, WmNews.Status.PUBLISHED, "审核通过");
    }

    /**
     * 保存app端相关的文章数据
     */
    private ResponseResult<Long> saveAppArticle(WmNews wmNews) {
        ArticleDto dto = BeanUtil.toBean(wmNews, ArticleDto.class);
        //文章的布局
        dto.setLayout(wmNews.getType());
        //频道
        WmChannel wmChannel = this.wmChannelService.getById(wmNews.getChannelId());
        if (wmChannel != null) {
            dto.setChannelName(wmChannel.getName());
        }

        //作者
        dto.setAuthorId(wmNews.getUserId().longValue());
        WmUser wmUser = this.wmUserService.getById(wmNews.getUserId());
        if (wmUser != null) {
            dto.setAuthorName(wmUser.getName());
        }

        //设置文章id
        dto.setId(wmNews.getArticleId());

        dto.setCreatedTime(new Date());
        return articleClient.saveArticle(dto);
    }

    /**
     * 审核图片内容
     */
    private boolean handleImageScan(List<String> images, WmNews wmNews) {
        if (CollUtil.isEmpty(images)) {
            return true;
        }
        for (String image : images) {

            try {
                //图片识别文字审核
                InputStream inputStream = HttpRequest.get(image)
                        .timeout(this.aliYunGreenConfig.getConnectTimeout())
                        .execute()
                        .bodyStream();
                BufferedImage imageFile = ImageIO.read(inputStream);
                String result = this.ocrService.doORC(imageFile);
                //审核是否包含自管理的敏感词
                boolean isSensitive = this.handleSensitiveScan(result, wmNews);
                if (!isSensitive) {
                    return false;
                }
            } catch (Exception e) {
                //出现异常，就认为违规内容
                return false;
            }
        }
        for (String image : images) {
            ImageCheckResult imageCheckResult = this.greenService.imageCheck(image);
            String riskLevel = imageCheckResult.getRiskLevel();
            if (StrUtil.equalsAny(riskLevel, "low", "medium") || StrUtil.isEmpty(riskLevel)) {
                String reason = StrUtil.format("当前文章中的图片存在不确定内容，风险等级为：{}，内容为：{}",
                        riskLevel,
                        StrUtil.join("|", imageCheckResult.getDescriptionList()));
                updateWmNews(wmNews, WmNews.Status.ADMIN_AUTH, reason);
                return false;
            } else if (StrUtil.equals(riskLevel, "high")) {
                String reason = StrUtil.format("当前文章的图片中存在违规内容，风险等级为：{}，内容为：{}",
                        riskLevel,
                        StrUtil.join("|", imageCheckResult.getDescriptionList()));
                updateWmNews(wmNews, WmNews.Status.FAIL, reason);
                return false;
            }
        }
        return true;
    }

    /**
     * 审核纯文本内容
     */
    private boolean handleTextScan(String content, WmNews wmNews) {
        //检测是否存在自定义的敏感词，如果存在，返回false
        List<WmSensitive> sensitiveList = this.wmSensitiveService.list();
        List<String> sensitiveStrList = CollStreamUtil.toList(sensitiveList, WmSensitive::getSensitives);
        //初始化
        //TODO: 没有必要每次都初始化，在容器启动后进行初始化，需要开发一个功能，新增敏感词时再次做初始化
        SensitiveWordUtil.initMap(sensitiveStrList);

        //匹配敏感词
        Map<String, Integer> matchResult = SensitiveWordUtil.matchWords(content);
        if (CollUtil.isNotEmpty(matchResult)) {
            //当前文章中存在违规内容，风险等级为：高风险，风险词为：【冰毒】出现【2】次，【法轮功】出现【1】次
            List<String> keyWorkList = matchResult.entrySet().stream()
                    .map(entry -> StrUtil.format("【{}】出现【{}】次", entry.getKey(), entry.getValue()))
                    .collect(Collectors.toList());

            String reason = StrUtil.format("当前文章中存在违规内容，风险等级为：高风险，风险词为：{}", StrUtil.join("，", keyWorkList));
            updateWmNews(wmNews, WmNews.Status.FAIL, reason);

            return false;
        }

        //阿里云内容审核
        TextCheckResult textCheckResult = this.greenService.textCheck(content);
        String riskLevel = textCheckResult.getRiskLevel();
        if (StrUtil.equals(riskLevel, "none")) {
            return true;
        }

        //medium: 中风险，low：低风险 需要人工审核
        if (StrUtil.equalsAny(riskLevel, "medium", "low") || StrUtil.isEmpty(riskLevel)) {
            String reason = StrUtil.format("当前文章中存在不确定内容，风险等级为：{}，风险词为：{}",
                    riskLevel,
                    StrUtil.join("|", textCheckResult.getRiskWordsList()));
            this.updateWmNews(wmNews, WmNews.Status.ADMIN_AUTH, reason);
            return false;
        }

        //高风险 直接审核失败
        String reason = StrUtil.format("当前文章中存在违规内容，风险等级为：{}，风险词为：{}",
                riskLevel,
                StrUtil.join("|", textCheckResult.getRiskWordsList()));
        updateWmNews(wmNews, WmNews.Status.FAIL, reason);
        return false;
    }

    /**
     * 修改文章内容
     */
    private void updateWmNews(WmNews wmNews, WmNews.Status status, String reason) {
        wmNews.setStatus(status.getCode());
        wmNews.setReason(reason);
        this.wmNewsService.updateById(wmNews);
    }

    /**
     * 从自媒体文章的内容中提取文本和图片
     *
     * @param wmNews 文章对象
     * @param type   提取的类型，1-文本 2-图片
     * @return 数据列表
     */
    private List<String> extractTextOrImages(WmNews wmNews, int type) {
        String content = wmNews.getContent();
        List<ContentBean> contentBeanList = JSONUtil.toList(content, ContentBean.class);
        List<String> list = StreamUtil.of(contentBeanList)
                .filter(contentBean -> {
                    if (type == CONTENT_TYPE_TEXT) {
                        return StrUtil.equals(contentBean.getType(), "text");
                    }
                    return StrUtil.equals(contentBean.getType(), "image");
                })
                .map(ContentBean::getValue)
                .collect(Collectors.toList());
        if (type == CONTENT_TYPE_TEXT) {
            list.add(wmNews.getTitle());
        } else if (type == CONTENT_TYPE_IMAGE) {
            //添加封面图片
            list.addAll(StrUtil.split(wmNews.getImages(), ','));
        }
        return list;
    }

    /**
     * 自管理的敏感词审核
     */
    private boolean handleSensitiveScan(String content, WmNews wmNews) {
        //获取所有的敏感词
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));
        List<String> sensitiveList = CollStreamUtil.toList(wmSensitives, WmSensitive::getSensitives);

        //初始化敏感词库
        SensitiveWordUtil.initMap(sensitiveList);

        //查看文章中是否包含敏感词
        Map<String, Integer> map = SensitiveWordUtil.matchWords(content);
        System.out.println(map);
        if (!map.isEmpty()) {
            // 获取所有的键
            Set<String> keys = map.keySet();
            // 将所有的键拼接成一个字符串
            String keysString = String.join(",", keys);
//            输出查到的所有敏感词
            String reason = StrUtil.format("当前文章中存在违规内容，风险词为：{}", keysString);
            updateWmNews(wmNews, WmNews.Status.FAIL, reason);
            return false;
        }
        return true;
    }
}
